'''
Created on Jan 10, 2020
@author: Liang.Li
'''
from decimal import Decimal

class ObjectMeta(type):
    def __new__(cls, name, bases, attrs):
        if '__slots__' in attrs:
            a = set(n for n in attrs['__slots__'] if not n.startswith('_'))
        else:
            attrs['__slots__'] = ()
            a = set()
        for b in bases:
            if hasattr(b, '__predefined_public_attribute_names__'):
                a.update(n for n in b.__predefined_public_attribute_names__)
            else:
                if hasattr(b, '__slots__'):
                    a.update(n for n in b.__slots__ if not n.startswith('_'))
                if hasattr(b, '__dict__'):
                    a.update(n for n in b.__dict__ if not (n.startswith('_') or hasattr(b.__dict__[n], '__call__')))
        attrs['__predefined_public_attribute_names__'] = tuple(a)
        return type.__new__(cls, name, bases, attrs)

class Object(object, metaclass=ObjectMeta):
    __lib = __import__('json', fromlist=('dumps', 'loads', 'JSONEncoder'))
    
    class __JSONEncoder(__lib.JSONEncoder):
        def default(self, o):
            if isinstance(o, Object):
                return o.attributes()
            if isinstance(o, bytes):
                return o.decode()
            if isinstance(o, Decimal):
                return float(o)
            return super().default(o)
    
    @classmethod
    def stringify(cls, e):
        return cls.__lib.dumps(e, cls=cls.__JSONEncoder)
    @classmethod
    def parse(cls, json, clazz=None):
        d = cls.__lib.loads(json)
        if isinstance(clazz, type):
            o = clazz()
            for k in d:
                if hasattr(clazz, k):
                    setattr(o, k, d[k])
            return o
        return d
    
    def __name_value_pairs(self, names):
        for name in names:
            if hasattr(self, name):
                yield (name, getattr(self, name))
    def attributes(self, names=None):
        if isinstance(names, str):
            names = names.split(',')
        if isinstance(names, (list, tuple)) and names:
            return {k: v for k, v in self.__name_value_pairs(n.strip() for n in names if n.strip() in self.__predefined_public_attribute_names__)}
        return {k: v for k, v in self.__name_value_pairs(self.__predefined_public_attribute_names__)}
    def __contains__(self, name):
        return hasattr(self, name)
    def __repr__(self):
        return self.stringify(self)

class Entity(Object):
    def __iter__(self):
        for k in self.__predefined_public_attribute_names__:
            if hasattr(self, k):
                yield k
    def __len__(self):
        return len([k for k in self.__iter__()])
    def __getitem__(self, name):
        if hasattr(self, name):
            return getattr(self, name)
        raise AttributeError(name)
    def __setitem__(self, name, value):
        if hasattr(self.__class__, name):
            setattr(self, name, value)
        else:
            raise AttributeError(name)
    def __delitem__(self, name):
        if hasattr(self, name):
            delattr(self, name)
        else:
            raise AttributeError(name)
    def to_json(self, names=None):
        return self.stringify(self.attributes(names))
